//==============================================================================
//
//          DJSON - the JSON reader, writer and tree in the ofc-library
//
//               Copyright (C) 2011  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-11 16:59:46 $ $Revision: 1.31 $
//
//==============================================================================

#include "ofc/DJSON.h"

#include "ofc/DText.h"

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DSource.h"

#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"

#define HAVE_DJSON (1)


#ifdef HAVE_DJSON

#define DJSON_OBJECT       1
#define DJSON_ARRAY        2
#define DJSON_STRING       3
#define DJSON_NUMBER       4
#define DJSON_BOOL         5
#define DJSON_NULL         6

#define DJSON_UNEXPECTED_CHAR    -1
#define DJSON_MISSING_END_OBJECT -2
#define DJSON_MISSING_END_ARRAY  -3
#define DJSON_INVALID_VALUE      -4
#define DJSON_MISSING_VALUE      -5
#define DJSON_INVALID_NUMBER     -6
#define DJSON_MISSING_STRING     -7
#define DJSON_MISSING_COLON      -8
#define DJSON_INVALID_STRING     -9
#define DJSON_INVALID_NAME      -10

//
// The DJSONHandler protocol specifies the methods a JSON handler
// must implement for processing the contents of a JSON source.
//

@protocol DJSONHandler

//
// Process the start of a JSON document
//
// @return success
// 
- (BOOL) startDocument;

//
// Process the end of a JSON document
// 
// @return success
// 
- (BOOL) endDocument;

//
// Process the start of an object
// 
// @return success
// 
- (BOOL) startObject;

//
// Process the end of an object
// 
// @return success
// 
- (BOOL) endObject;

//
// Process the start of an array
// 
// @return success
// 
- (BOOL) startArray;

//
// Process the end of an array
// 
// @return success
// 
- (BOOL) endArray;

//
// Process the name for a value in an object
// 
// @param name     the name of a value
//
// @return success
//
- (BOOL) name :(const char *) name;

//
// Process a string value
// 
// @param value    the value of the string
// 
// @return success
// 
- (BOOL) string :(const char *) value;

//
// Process a number value
// 
// @param value    the value of the number
// 
// @return success
// 
- (BOOL) number :(double) value;

//
// Process a boolean value
// 
// @param value    the value of the boolean
// 
// @return success
// 
- (BOOL) boolean :(BOOL) value;

//
// Process a null pointer value
// 
// @return success
// 
- (BOOL) nullptr;

// 
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 
- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;

@end




//
// The DJSONReader class implements methods for parsing a JSON file.
//
// @example
// ToDo: example
// 
@interface DJSONReader : DObject
{
@private
  id <DJSONHandler>    _handler;      // the reference to the xml handler
  DSource             *_srce;         // the source
  BOOL                 _eof;          // the end of file for source
  DText               *_string;       // the string during parsing
  int                  _error;        // the last error
}
#endif

#endif


#ifdef HAVE_DJSON


@implementation DJSONReader


//// Constructors

//
// Initialise a JSON reader
//
// @return the object
//

- (DJSONReader *) init
{
  [super init];

  _handler      = nil;
  _srce         = nil;
  _eof          = NO;
  _string       = [DText new];
  _error        = 0;
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//
- free
{
  _handler = nil;

  if (_srce != nil)
  {
    [_srce free]; _srce = nil;
  }
  [_string free]; _string = nil;
  
  return [super free];
}


//// Member methods


//// Parser methods

#if _PRIVATE_

//  
// Report an error  
//   
// @param number     the error number
//
// @return the object
// 
- reportError :(int) number
{
  [_handler error :number :[_srce name] :[_srce lineNumber] :[_srce columnNumber]];
  
  _error = number;
  _eof   = YES;
  
  return self;
}

//  
// Skip spaces in the source  
//   
// @return the current line or NULL
// 
- (const char *) skipWhitespace
{
  BOOL skipping = YES;
  
  while ((!_eof) && (skipping))
  {
    while ((!_eof) && ([_srce isLineScanned]))
    {
      _eof = ![_srce nextLine];
    }
    
    if (!_eof)
    {
      int cnt = 0;
      
      const char *line = [_srce line];
      
      while (isspace(*line))
      {
        line++;
        cnt++;
      }
      
      [_srce scanned :cnt];
      
      skipping = [_srce isLineScanned];
    }
  }

  return (_eof ? NULL : [_srce line]);
}

//
// Parse a string
//
// @return the string (or nil for error)
//
- (DText *) doString
{
  const char *line = [_srce line];
  
  DText   *string = nil;
  
  int      scanned = 0;
  unsigned unicode = 0;

  enum { normal, escaped, hex1, hex2, hex3, hex4 } mode = normal;

  BOOL done = NO;
  BOOL err  = NO;
  
  [_string clear];
  while ((*line != EOS) && (!done) && (!err))
  {
    if (mode == normal)
    {
      if (*line == '\\')
      {
        mode = escaped;
      }
      else if (*line == '"')
      {
        done = YES;
      }
      else
      {
        [_string push :*line];
      }
    }
    else if (mode == escaped)
    {
      mode = normal;
        
      char ch = *line;
      switch(ch)
      {
        case 'b' : ch = '\b'; break;
        case 'f' : ch = '\f'; break;
        case 'n' : ch = '\n'; break;
        case 'r' : ch = '\r'; break;
        case 't' : ch = '\t'; break;
        case 'u' : ch = 0;    mode = hex1; unicode = 0; break;
        case '"' : ch = '"';  break;
        case '\\': ch = '\\'; break;
        case '/' : ch = '/';  break;
        default  : err = YES; break;
      }
        
      if (ch != 0)
        [_string push :ch];
    }
    else // hex?
    {
      unicode *= 16;
        
      char ch = *line;
      if ((ch >= '0') && (ch <= '9'))
        unicode += (int)(ch - '0');
      else if ((ch >= 'a') && (ch <= 'f'))
        unicode += (int)(ch - 'a' + 10);
      else if ((ch >= 'A') && (ch <= 'F'))
        unicode += (int)(ch - 'A' + 10);
      else
        err = YES;
        
      if (mode == hex4)
      {
        [_string push :unicode];
        mode = normal;
      }
      else
        mode++;
    }
    
    line++;
    scanned++;
  }
  
  if (done)
  {
    string = _string;
    
    [_srce scanned :scanned];
  }
  else
  {
    [self reportError :DJSON_INVALID_STRING];
  }
  
  return string;
}

//
// Parse a string
//
// @return the object
//
- (id) parseString
{
  DText *string = [self doString];
  
  if (string != nil)
  {
    [_handler string :[_string cstring]];
  }
  else
  {
    [self reportError :DJSON_INVALID_STRING];
  }
  
  return self;
}

//
// Parse a name
//
// @return the object
//
- (id) parseName
{
  DText *string = [self doString];
  
  if (string != nil)
  {
    [_handler name :[_string cstring]];
  }
  else
  {
    [self reportError :DJSON_INVALID_NAME];
  }
  
  return self;
}

//
// Parse a number
//
// @return the object
//
- (id) parseNumber
{
  const char *line = [_srce line];
  
  BOOL ok = NO;
  
  [_string clear];
  
  if (*line == '-')
  {
    [_string push :*line++];
  }
  
  while ((*line >= '0') && (*line <= '9'))
  {
    ok = YES;
    
    [_string push :*line++];
  }
  
  if (*line == '.')
  {
    [_string push :*line++];
    
    while ((*line >= '0') && (*line <= '9'))
    {
      [_string push :*line++];
    }
  }
  
  if ((*line == 'e') || (*line == 'E'))
  {
    [_string push :*line++];
    
    if ((*line == '-') || (*line == '+'))
    {
      [_string push :*line++];
    }
    
    while ((*line >= '0') && (*line <= '9'))
    {
      [_string push :*line++];
    }
  }
  
  if (ok)
  {
    char *ptr;
    
    [_srce scanned :[_string length]];
   
    [_handler number :strtod([_string cstring], &ptr)];
  }
  else
  {
    [self reportError :DJSON_INVALID_NUMBER];
  }
  
  return self;
}
#endif
  

//
// Parse a value
//
// @return is parsing done ?
//
- (id) parseValue
{
  const char *line = [self skipWhitespace];
  
  if (line != NULL)
  {
    if (strncmp(line, "true", 4) == 0)
    {
      [_srce scanned :4];
      
      [_handler boolean :YES];
    }
    else if (strncmp(line, "false", 5) == 0)
    {
      [_srce scanned :5];
      
      [_handler boolean :NO];
    }
    else if (strncmp(line, "null", 4) == 0)
    {
      [_srce scanned :4];
      
      [_handler nullptr];
    }
    else if (*line == '"')
    {
      [_srce scanned :1];
      
      [self parseString];
    }
    else if (*line == '{')
    {
      [_srce scanned :1];
      
      [self parseObject];
    }
    else if (*line == '[')
    {
      [_srce scanned :1];
      
      [self parseArray];
    }
    else if ((*line == '-') || ((*line >= '0') && (*line <= '9')))
    {
      [self parseNumber];
    }
    else
    {
      [self reportError :DJSON_INVALID_VALUE];
    }
  }
  else
  {
    [self reportError :DJSON_MISSING_VALUE];
  }
  
  return self;
}

//
// Parse a pair: name and value
//
// @return is parsing done ?
//
- (id) parsePair
{
  const char *line = [self skipWhitespace];
  
  if ((line != NULL) && (*line == '"'))
  {
    [_srce scanned :1];
      
    [self parseName];
    
    line = [self skipWhitespace];
    
    if (*line == ':')
    {
      [_srce scanned :1];
      
      [self parseValue];
    }
    else
    {
      [self reportError :DJSON_MISSING_COLON];
    }
  }
  else
  {
    [self reportError :DJSON_MISSING_STRING];
  }

  return self;
}

//
// Parse an object
//
// @return is parsing done ?
//
- (id) parseObject
{
  [_handler startObject];
  
  const char *line = [self skipWhitespace];
  
  if (line != NULL)
  {
    if (*line != '}')
    {
      [self parsePair];
      
      line = [self skipWhitespace];
      
      while ((line != NULL) & (*line == ','))
      {
        [_srce scanned :1];
        
        [self parsePair];
      
        line = [self skipWhitespace];
      }
    }
    
    if (*line == '}')
    {
      [_srce scanned :1];

      [_handler endObject];
    }
    else
    {
      [self reportError :DJSON_MISSING_END_OBJECT];
    }
  }
  else
  {
     [self reportError :DJSON_MISSING_END_OBJECT];
  }
  
  return self;
}

//
// Parse an array
//
// @return is parsing done ?
//
- (id) parseArray
{
  [_handler startArray];
  
  const char *line = [self skipWhitespace];
  
  if (line != NULL)
  {
    if (*line != ']')
    {
      [self parseValue];
      
      line = [self skipWhitespace];
      
      while ((line != NULL) && (*line == ','))
      {
        [_srce scanned :1];
        
        [self parseValue];
        
        line = [self skipWhitespace];
      }
    }
    
    if (*line == ']')
    {
      [_srce scanned :1];
      
      [_handler endArray];
    }
    else
    {
      [self reportError :DJSON_MISSING_END_ARRAY];
    }
  }
  else
  {
    [self reportError :DJSON_MISSING_END_ARRAY];
  }
  
  return self;
}


//
// Parse a JSON file
// 
// @param source     the source of the JSON file
// @param name       the name of the JSON file
// @param handler    the JSON handler
// 
// @return success
// 

- (BOOL) parse :(id <DTextReadable>) source :(const char *) name :(id <DJSONHandler>) handler
{
  BOOL ok   = NO;

  if (source == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "source");
  }
  else if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if (handler == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "handler");
  }
  else
  {
    _srce    = [DSource new];
    _handler = handler;
    _error   = 0;
    
    if ([_srce set :source :name])
    {
      ok   = YES;
      _eof = NO;
      
      [_handler startDocument];
      
      while (!_eof)
      {
        const char *line = [self skipWhitespace];
    
        if (line != NULL)
        {
          if (*line == '{')
          {
            [_srce scanned :1];
        
            [self parseObject];
          }
          else if (*line == '[')
          {
            [_srce scanned :1];
        
            [self parseArray];
          }
          else
          {
            [self reportError :DJSON_UNEXPECTED_CHAR];
          }
        }
      }
    }
    [_handler endDocument];
    
    _handler = nil;
    
    [_srce free]; _srce = nil;
    
    ok = (_error == 0);
  }
  
  return ok;
}

//
// Return the current line number during parsing
// 
// @return the current line number (or 0)
// 
- (int) lineNumber
{
  return ((_srce != nil) ? [_srce lineNumber] : 0);
}

//
// Return the current column number during parsing
// 
// @return the current column number
// 
- (int) columnNumber
{
  return ((_srce != nil) ? [_srce columnNumber] : 0);
}

//
// Return the name of the source during parsing
// 
// @return the current source name (or NULL)
// 
- (const char *) name
{
  return ((_srce != nil) ? [_srce name] : NULL);
}

//
// Translate the error to a text string
// 
// @param error    the error number
// 
// @return the text string
// 

+ (const char *) errorToString :(int) error
{
  switch(error)
  {
    case DJSON_UNEXPECTED_CHAR    : return "Unexpected character"; 
    case DJSON_MISSING_END_OBJECT : return "Missing '}' character";
    case DJSON_MISSING_END_ARRAY  : return "Missing ']' character";
    case DJSON_INVALID_VALUE      : return "Invalid value";
    case DJSON_MISSING_VALUE      : return "Missing value";
    case DJSON_INVALID_NUMBER     : return "Invalid number";
    case DJSON_MISSING_STRING     : return "Missing string";
    case DJSON_MISSING_COLON      : return "Missing colon";
    case DJSON_INVALID_STRING     : return "Invalid string";
    case DJSON_INVALID_NAME       : return "Invalid name";
  }

  return NULL;
}

@end

#endif

#if 0

#define DJSON_OBJECT_OR_ARRAY   (0)
#define DJSON_FIRST_PAIR_NAME   (1)
#define DJSON_FIRST_PAIR_VALUE  (2)
#define DJSON_NEXT_PAIR_NAME    (3)
#define DJSON_NEXT_PAIR_VALUE   (4)
#define DJSON_FIRST_VALUE       (5)
#define DJSON_NEXT_VALUE        (6)

- init
{
  _state = DJSON_OBJECT_OR_ARRAY;
}
//
// Write the start of a JSON document
//
// @return success
// 
- (BOOL) startDocument
{
  return(_state == DJSON_OBJECT_OR_ARRAY);
}

//
// Write the end of a JSON document
// 
// @return success
// 
- (BOOL) endDocument
{
  return (_state == DJSON_OBJECT_OR_ARRAY);
}

//
// Process the start of an object
// 
// @return success
// 
- (BOOL) startObject
{
  BOOL ok = ((_state == DJSON_OBJECT_OR_ARRAY) || 
             (_state == DJSON_FIRST_PAIR_VALUE) ||
             (_state == DJSON_NEXT_PAIR_VALUE) ||
             (_state == DJSON_FIRST_VALUE) ||
             (_state == DJSON_NEXT_VALUE));
  
  if (ok)
  {
    [_dest writeChar :'{'];
    
    // push state
    _state = DJSON_FIRST_PAIR_NAME;
  }
  
  return (ok);
}
    

//
// Write the end of an object
// 
// @return success
// 
- (BOOL) endObject
{
  BOOL ok = ((_state == DJSON_FIRST_PAIR_NAME) ||
             (_state == DJSON_NEXT_PAIR_NAME));
             
  if (ok)
  {
    [_dest writeChar :'}'];
    
    // _state = pop;
  }
  
  return (ok);
}

//
// Write the start of an array
// 
// @return success
// 
- (BOOL) startArray
{
  BOOL ok = ((_state == DJSON_OBJECT_OR_ARRAY) || 
             (_state == DJSON_FIRST_PAIR_VALUE) ||
             (_state == DJSON_NEXT_PAIR_VALUE) ||
             (_state == DJSON_FIRST_VALUE) ||
             (_state == DJSON_NEXT_VALUE));
  
  if (ok)
  {
    [_dest writeChar :'['];
    
    // push state
    _state = DJSON_FIRST_VALUE;
  }
  
  return (ok);
}

//
// Write the end of an array
// 
// @return success
// 
- (BOOL) endArray
{
  BOOL ok = ((_state == DJSON_FIRST_VALUE) ||
             (_state == DJSON_NEXT_VALUE));
             
  if (ok)
  {
    [_dest writeChar :']'];
    
    // _state = pop;
  }
  
  return (ok);
}

//
// Write the name for a value in an object
// 
// @param name     the name of a value
//
// @return success
//
- (BOOL) name :(const char *) name
{
  BOOL ok = ((_state == DJSON_FIRST_PAIR_NAME) ||
             (_state == DJSON_NEXT_PAIR_NAME));

  if (ok)
  {
    if (_state == DJSON_NEXT_PAIR_NAME)
    {
      [_dest writeChar :','];
    }
    [self writeString :name];
    
    _state = _state == DJSON_FIRST_PAIR_NAME ? DJSON_FIRST_PAIR_VALUE : DJSON_NEXT_PAIR_VALUE);
  }
  
  return (ok);
}

//
// Write a string value
// 
// @param value    the value of the string
// 
// @return success
// 
- (BOOL) string :(const char *) value
{
  BOOL ok = ((_state == DJSON_FIRST_PAIR_VALUE) ||
             (_state == DJSON_NEXT_PAIR_VALUE) ||
             (_state == DJSON_FIRST_VALUE) ||
             (_state == DJSON_NEXT_VALUE));
             
  if (ok)
  {
    if (_state == DJSON_NEXT_VALUE)
    {
      [_dest writeChar : ','];
    }
    [self writeString :value];
    
    if ((_state == DJSON_FIRST_PAIR_VALUE) || (_state == DJSON_NEXT_PAIR_VALUE))
    {
      _state = DJSON_NEXT_PAIR_NAME;
    }
    else // DJSON_FIRST_VALUE || DJSON_NEXT_VALUE
    {
      _state = DJSON_NEXT_VALUE
    }
  }
  
  return (ok);
}

//
// Process a number value
// 
// @param value    the value of the number
// 
// @return success
// 
- (BOOL) number :(double) value;

//
// Process a boolean value
// 
// @param value    the value of the boolean
// 
// @return success
// 
- (BOOL) boolean :(BOOL) value;

//
// Process a null pointer value
// 
// @return success
// 
- (BOOL) nullptr;

#endif

/*==========================================================================*/
